#!/usr/bin/env python
import random
from nodos_gramatica import *
from cargador_gramaticas import GramaticaELL1
from color import *


# genera gramaticas random o a partir de los nt y los arboles aplanados
class generadorGramaticas:
        
    def generarGramaticaRandom(self,altura=3,cantNoTerminales=2):
        noTerminales = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[0:cantNoTerminales]
        producciones = {}
        for nt in noTerminales:
                producciones[nt] = self.crearArbol(altura,noTerminales)
        return producciones 
    
    def generarGramaticaFromString(self, noTerminales, s):
        producciones = {}
        l = list(s)
        for nt in noTerminales:
                producciones[nt] = self.crearArbolFromString(l)
        return producciones
    
    def crearArbolFromString(self, s):
        
        if s[0].isupper():
            x = s.pop(0)
            return SimboloNoTerminal(x)
        elif s[0].islower():
            x = s.pop(0)
            return SimboloTerminal(x)
        elif s[0] == "_":
            s.pop(0)
            return SimboloLambda()
        elif s[0] == '?':
            s.pop(0)
            hijo = self.crearArbolFromString(s)
            return OperandoPreg(hijo)
        elif s[0] == '*':
            s.pop(0)
            hijo = self.crearArbolFromString(s)
            return OperandoEstrella(hijo)
        elif s[0] == '+':
            s.pop(0)
            hijo = self.crearArbolFromString(s)
            return OperandoMas(hijo)
        elif s[0] == "|":
            s.pop(0)
            izq = self.crearArbolFromString(s)
            der = self.crearArbolFromString(s)
            return OperandoUnion(izq,der)
        elif s[0] == ".":
            s.pop(0)
            izq = self.crearArbolFromString(s)
            der = self.crearArbolFromString(s)
            return OperandoConcat(izq,der)
        else:
            raise Exception("Caracter que nada tenia que ver " + s[0])
            
    def dumpToString(self,producciones):
        s = ""
        for nt in producciones:
            x = str(producciones[nt])
            
            s += nt + ":" + x +";\n"
        print s
        return s
    
    def crearArbol(self, altura, noTerminales, tipoDeNodo = None):
        tipoSimbolos = [SimboloTerminal, SimboloNoTerminal]
        unarios = [OperandoPreg,OperandoMas,OperandoEstrella]
        if altura == 0:
            
            tipo = random.choice([SimboloTerminal, SimboloNoTerminal,SimboloLambda])
            if tipo == SimboloTerminal:
                simbolo = random.choice(["a","b","c","d","e","f"])
                return tipo(simbolo)
                
            elif tipo == SimboloLambda:
                return  tipo()
            else:
                simbolo = random.choice(noTerminales)
                return tipo(simbolo)
            return tipo(simbolo)
        operandos = [OperandoPreg,OperandoMas,OperandoEstrella,OperandoConcat, OperandoUnion]   
        if tipoDeNodo == None:
            raiz = random.choice(operandos)
            return self.crearArbol(altura,noTerminales,raiz)
        
        if tipoDeNodo in [OperandoPreg,OperandoMas,OperandoEstrella]:
            tipoDeNodoHijo = random.choice(operandos)
            hijo = self.crearArbol(altura-1,noTerminales,tipoDeNodoHijo)
            if hijo.__class__ == SimboloLambda().__class__:
                hijo = SimboloTerminal(random.choice(["a","b","c","d","e","f"]))
            return tipoDeNodo(hijo)
            
        elif tipoDeNodo in [OperandoConcat, OperandoUnion] :
            tipoDeNodoDerecho = random.choice(operandos)
            tipoDeNodoIzquierdo = random.choice(tipoSimbolos)
            izq = self.crearArbol(altura-1,noTerminales,tipoDeNodoIzquierdo)
            der = self.crearArbol(altura-1,noTerminales,tipoDeNodoDerecho)
            # nuestra gramatica nunca hace _.Algo, siempre deja Algo solamente
            # lo mismo si el lambda esta a la derecha
            if tipoDeNodo == OperandoConcat and izq.__class__ == SimboloLambda().__class__ :
                izq = SimboloTerminal(random.choice(["a","b","c","d","e","f"]))
            if tipoDeNodo == OperandoConcat and der.__class__ == SimboloLambda().__class__ :
                der = SimboloTerminal(random.choice(["a","b","c","d","e","f"]))
                
            return tipoDeNodo(izq,der)
        
        elif tipoDeNodo in  [SimboloNoTerminal]:
            simbolo = random.choice(noTerminales)
            return tipoDeNodo(simbolo)
        
        elif tipoDeNodo in [SimboloTerminal]:
            simbolo = random.choice(["a","b","c","d","e","f"])
            return tipoDeNodo(simbolo)
        elif tipoDeNodo in [SimboloLambda]:
            return tipoDeNodo()
        else:
            raise "me rompi"

#compara un arbol con una gramatica
class Comparador:
    def __init__(self,gram, p):
        self.gram = gram
        self.p = p
        
    def comparar(self):
        p = self.p
        gram = self.gram
        
        for nt in p:
            if not nt in gram.noTerminales:
                self.error = "la gramtica no cargo al no terminal %s" % nt
                return False
            if not (self.compararArbol(p[nt], gram.producciones[nt])):
                return False
            else:
                return True
        
    def compararArbol(self,arbol1, arbol2):
        # no sirve comparar los arboles porque aca queremos que dos concat sean iguales aunque no arbol1 is arbol2
        if not (arbol1.__class__  == arbol2.__class__):
            print arbol1.__class__
            print arbol2.__class__ 
            self.error = "el nodo %s no unifica con el nodo %s" % (arbol1, arbol2)
            return False
        elif arbol1.__class__ in [SimboloTerminal, SimboloNoTerminal]:
            if not arbol1.simbolo == arbol2.simbolo:
                self.error = "los nodos tienen diferente simbolo: %s y %s" % (arbol1.simbolo, arbol2.simbolo)
                return False
            else:
                return True
        elif arbol1.__class__ in   [OperandoPreg,OperandoMas,OperandoEstrella]:
            return self.compararArbol(arbol1.hijo,arbol2.hijo)
        elif arbol1.__class__ in  [SimboloLambda]:
            return True
        else:
            izq = self.compararArbol(arbol1.izq, arbol2.izq)
            if izq:
                return self.compararArbol(arbol1.der, arbol2.der)
            else:
                return izq
            
#tests randoms de generacion de arboles
class TestRandomsInputGrammars:
    def __init__(self, nombre,cantidad=10,lengthMin=3, lenghtMax=3,noTerminalesMin = 3,noTerminalesMax=3):
        self.nombre = nombre
        self.g = generadorGramaticas()
        self.lengthMin = lengthMin
        self.lengthMax =lenghtMax
        self.ntMin = noTerminalesMin
        self.ntMax = noTerminalesMax
        self.cantidad = cantidad
        
    def runTests(self):
        term = TerminalController()
        for i in range(self.cantidad):
            alto = random.randint(self.lengthMin,self.lengthMax)
            nt = random.randint(self.ntMin,self.ntMax)
            p = self.g.generarGramaticaRandom(alto,nt)
            s = self.g.dumpToString(p)
            s = s.replace("_","")
            try:
                 gram = GramaticaELL1(s)
            except:
                print "imposible generar gramatica a partir de %s" % s
                return
            c = Comparador(gram,p)
            if not c.comparar():          
                print term.render("${RED}ERROR %s" % self.nombre) ,
                print term.render("${NORMAL}") 
                print "esperados", p
                print "obtenido", gram.producciones
                print c.error
                return
            
        print term.render("${GREEN}OK!(%s: %s)") % (self.nombre,self.cantidad),
        print term.render("${NORMAL}") 
        
#tests de generacion de arboles a partir de strings 
class TestInputGrammar:
    def __init__(self,nombre,s):
        self.nombre = nombre
        self.g = generadorGramaticas()
        self.tests = s
    
    def runTests(self):
        term = TerminalController()
        if self.tests.__class__ == [].__class__:
            i=1
            for (nt,s) in self.tests:
                try:
                    p=self.g.generarGramaticaFromString(nt,s)
                except:
                    print "imposible generar un arbol a parti del string %s para los no terminales %s" % (s,nt)
                    return
                s = self.g.dumpToString(p)
                s = s.replace("_","")
                try:
                    gram = GramaticaELL1(s)
                except:
                    print "imposible generar gramatica a partir de %s" % s
                    return
                c = Comparador(gram,p)
                if not c.comparar():        
                    print term.render("${RED}ERROR (%s, caso: %s)"%(self.nombre,i)) ,
                    print term.render("${NORMAL}") 
                    print "esperados", p
                    print "obtenido", gram.producciones
                    print c.error
                    return
                i=i+1
            
            print term.render("${GREEN}OK!(%s: %s)"%(self.nombre,len(self.tests)) ),
            print term.render("${NORMAL}") 
        else:
            (nt, s) = self.tests
            try:
                 p=self.g.generarGramaticaFromString(nt,s)
            except:
                print "imposible generar un arbol a parti del string %s para los no terminales %s" % (s,nt)
                return
                
            s = self.g.dumpToString(p)
            s = s.replace("_","")
            try:
                gram = GramaticaELL1(s)
            except:
                print "imposible generar gramatica a partir de %s" % s
                return
            c = Comparador(gram,p)
            if not c.comparar():
                
                print term.render("${RED}ERROR(%s)"%(self.nombre)) ,
                print term.render("${NORMAL}") 
                print "esperados", p
                print "obtenido", gram.producciones
                print c.error
                return
            
            print term.render("${GREEN}OK!(%s)"%(self.nombre)) ,
            print term.render("${NORMAL}") 
                
# chequea que la gramatica falle al parsear una cierta gramatica defectuosa
class TestErrorInputGrammar:
    def __init__(self,nombre,s,errorEsperado=None):
        self.nombre = nombre
        self.s = s
        self.errorEsperado = errorEsperado
        
    def runTests(self):
        errorEsperado = self.errorEsperado
        nombre = self.nombre
        s =self.s
        term = TerminalController()
        if errorEsperado is None:
            try:
                gram = GramaticaELL1(s)
                print term.render("${RED} ERROR (%s)${NORMAL}"% nombre),
                print "la gramatica se ha creado con exito"
                print gram
                return False
            except Exception, e:
                print term.render("${GREEN}OK!(%s)${NORMAL}" % nombre),
                print e
        else:
            try:
                gram = GramaticaELL1(s)
                print term.render("${RED}ERROR(%s) ${NORMAL}"% nombre),
                print "la gramatica se ha creado con exito"
                print gram
                return False
            except errorEsperado, e:
                print term.render("${GREEN}OK!(%s)${NORMAL}"% nombre),
                print e
            except Exception, e:
                print term.render("${RED}ERROR(%s) ${NORMAL}" % nombre),
                print "El error no fue el esperado"
                print e
            
         
    
        
#Ejemplos de uso        
t= TestRandomsInputGrammars("testRandom")
t.runTests()
t1 = TestInputGrammar("testConUnaSolaTupla",("A","|.aA?b"))
t1.runTests()
t2 = TestInputGrammar("testConUnaListaDeTuplas",[("A","|.aA?b"), ("BA","|.aAb_"),("S","|_|_|__")] )
t2.runTests()
t3 = TestErrorInputGrammar("testDeError", "A:b|2;;")
t3.runTests()
            
gramatica = GramaticaELL1("A:B|B|AB;C:(eA)?;B:(C|f)+;")
print gramatica.generarDot()
            
            


                
            
